using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace WolfCarol.AvgToolkit.Command
{
    public enum ExecutionOrders
    {
        Sequence = 0,
        Parallel = 1
    }

    [AttributeUsage(AttributeTargets.Method)]
    public class CommandAttribute : Attribute
    {
        public readonly string commandName;

        public readonly ExecutionOrders order;

        public CommandAttribute(string _commandName, ExecutionOrders _order)
        {
            commandName = _commandName;
            order = _order;
        }
    }

    public class CommandManager : MonoBehaviour
    {
        protected static Dictionary<string, MethodInfo> s_commandDictionary;

        protected string[] m_commands;

        protected Dictionary<string, int> m_flagToIndex = new Dictionary<string, int>();

        protected bool m_canExecute;

        protected TextAsset m_screenplay;

        protected int m_index;

        public LinePresenter linePresenter;

        public ChoicePresenter choicePresenter;

        protected void Awake()
        {
            s_commandDictionary ??= Assembly.GetExecutingAssembly()
                                        .GetTypes()
                                        .SelectMany(type => type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly))
                                        .Where(method => Attribute.IsDefined(method, typeof(CommandAttribute)))
                                        .ToDictionary(method => (method.GetCustomAttribute(typeof(CommandAttribute)) as CommandAttribute).commandName);
        }

        public async void Next()
        {
            if (m_commands == null)
            {
                Debug.Log("Command list was empty.");
                return;
            }

            bool isChoice = false;

            while (true)
            {
                if ((m_canExecute == false || m_index >= m_commands.Length) && isChoice == false)
                {
                    return;
                }

                m_canExecute = false;

                if (m_commands[m_index].Contains('>'))
                {
                    isChoice = true;
                    int choiceSpliter = m_commands[m_index].IndexOf('>');
                    choicePresenter.PresentChoice(m_commands[m_index][0..choiceSpliter], m_commands[m_index][(choiceSpliter + 1)..]);
                    m_index++;
                    continue;
                }
                else if (isChoice)
                {
                    return;
                }

                if (m_commands[m_index][0] == '#')
                {
                    m_index++;
                    m_canExecute = true;
                    continue;
                }

                if (m_commands[m_index][0] == '@')
                {
                    string[] splited = m_commands[m_index][1..].Split(' ');
                    MethodInfo method = s_commandDictionary[splited[0]];
                    object obj = method.IsStatic ? null : GameObject.Find(splited[1].Replace('_', ' ')).GetComponent(method.DeclaringType);
                    ParameterInfo[] parameters = method.GetParameters();
                    object[] objs = new object[parameters.Length];
                    int parameterStartIndex = method.IsStatic ? 1 : 2;
                    string[] paras = splited[parameterStartIndex..];
                    for (int i = 0; i < parameters.Length; i++)
                    {
                        objs[i] = i < paras.Length ? Convert.ChangeType(paras[i], parameters[i].ParameterType) : parameters[i].DefaultValue;
                    }
                    ExecutionOrders exeType = (method.GetCustomAttribute(typeof(CommandAttribute)) as CommandAttribute).order;
                    if (exeType == ExecutionOrders.Sequence)
                    {
                        if (method.Invoke(obj, objs) is Task task)
                        {
                            await task;
                        }
                    }
                    else if (exeType == ExecutionOrders.Parallel)
                    {
                        method.Invoke(obj, objs);
                    }

                    m_index++;
                    m_canExecute = true;
                    continue;
                }

                if (m_commands[m_index].Contains(':'))
                {
                    int lineSpliter = m_commands[m_index].IndexOf(':');
                    await linePresenter.PresentLine(m_commands[m_index][0..lineSpliter], m_commands[m_index][(lineSpliter + 1)..]);
                    m_index++;
                    m_canExecute = true;
                    return;
                }
            }
        }

        protected void SetScreenplay(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return;
            }

            m_commands = text.Split('\n')
                            .Select(line => line.Trim())
                            .Where(line => line.Length != 0)
                            .ToArray();

            m_flagToIndex.Clear();
            for (int i = 0; i < m_commands.Length; i++)
            {
                if (m_commands[i][0] == '#')
                {
                    m_flagToIndex.Add(m_commands[i][1..], i);
                }
            }
        }

        public void Choose(string flag)
        {
            Goto(flag);
            m_canExecute = true;
            Next();
        }

        [Command("goto", ExecutionOrders.Sequence)]
        public void Goto(string flag)
        {
            if (m_flagToIndex.ContainsKey(flag) == false)
            {
                throw new ArgumentNullException($"Could not find {flag}.");
            }

            m_index = m_flagToIndex[flag];
        }

        [Command("wait", ExecutionOrders.Sequence)]
        public static async Task Wait(float time)
        {
            await Task.Delay((int)(time * 1000f));
        }
    }
}
